package de.encryptor.types;

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;

import org.apache.commons.lang.ArrayUtils;
import org.hibernate.Hibernate;
import org.hibernate.HibernateException;
import org.hibernate.type.NullableType;
import org.hibernate.usertype.ParameterizedType;
import org.hibernate.usertype.UserType;
import org.hibernate.util.EqualsHelper;
import org.jasypt.encryption.pbe.PBEStringEncryptor;
import org.jasypt.encryption.pbe.StandardPBEStringEncryptor;
import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH
/**
 *
 * Base class for <b>Hibernate 3</b> <tt>UserType</tt>s to store 
 * values as encrypted strings.
 * 
 * @since 1.2
 * @author Daniel Fern&aacute;ndez
 * @author Iv&aacute;n Garc&iacute;a S&aacute;inz-Aja
 * 
 */
public abstract class AbstractEncryptedAsStringType implements UserType, ParameterizedType {
	
	static final NullableType nullableType = Hibernate.STRING;
	static final int sqlType = nullableType.sqlType();
	static final int[] sqlTypes = [sqlType];
	private static def encryptorConfig = CH.config.encryptor
	private boolean initialized = false;
	
	protected static PBEStringEncryptor encryptor = null;
	
	/**
	 * Converts given String to its Object form.
	 * @param string the string value
	 * @return the object form of the passed String
	 */
	protected abstract Object convertToObject(String string);
	
	/**
	 * Converts given Object to its String form.
	 * @param object the object value
	 * @return the string form of the passes Object
	 */
	protected String convertToString(Object object) {
		return object == null ? null : object.toString();
	}
	
	public int[] sqlTypes() {
		return ArrayUtils.clone(sqlTypes);
	}
	
	public abstract Class returnedClass();
	
	public boolean equals(Object x, Object y) throws HibernateException {
		return EqualsHelper.equals(x, y);
	}
	
	public Object deepCopy(Object value) throws HibernateException {
		return value;
	}
	
	public Object assemble(Serializable cached, Object owner) throws HibernateException {
		if (cached == null) {
			return null;
		}
		return deepCopy(cached);
	}
	
	public Serializable disassemble(Object value) throws HibernateException {
		if (value == null) {
			return null;
		}
		return (Serializable) deepCopy(value);
	}
	
	public boolean isMutable() {
		return false;
	}
	
	public int hashCode(Object x) throws HibernateException {
		return x.hashCode();
	}
	
	public Object replace(Object original, Object target, Object owner) throws HibernateException {
		return original;
	}
	
	public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
		checkInitialization();
		String message = rs.getString(names[0]);
		return rs.wasNull() ? null : convertToObject(this.encryptor.decrypt(message));
	}
	
	public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
		checkInitialization();
		if (value == null) {
			st.setNull(index, sqlType);
		} else {
			st.setString(index, this.encryptor.encrypt(convertToString(value)));
		}
	}
	
	public synchronized void setParameterValues(Properties parameters) {
		//do nothing, maybe later
	}
	
	protected synchronized void checkInitialization() {
		if (!this.initialized) {
			this.encryptor = getEncryptor();
			this.initialized = true;
		}
	}
	
	public static StandardPBEStringEncryptor getEncryptor() {
		if (encryptor == null) {
			encryptor = new StandardPBEStringEncryptor();
			encryptor.algorithm=encryptorConfig.algorithm?encryptorConfig.algorithm:"PBEWithMD5AndTripleDES"
			encryptor.password=encryptorConfig.password?encryptorConfig.password:"jasypt"
			encryptor.keyObtentionIterations=encryptorConfig.keyObtentionIterations?encryptorConfig.keyObtentionIterations:1000
			encryptor.initialize();
		}
		return encryptor
	}
}
